<template>
  <div class="chartPie3D-box" :id="props.id"></div>
</template>

<script setup>
import { ref, onMounted, computed } from "vue";
import * as echarts from "echarts";
import "echarts-gl";

const props = defineProps({
  id: String,
  options: Object,
});

const sourceData = ref(props.options); //饼图数据
console.log("pr", props.options);
const option = ref(null); // 配置项
const hollow = ref(0.6);
const pieHeight = ref(16); // 饼图高度 0 自适应高度  其他值固定高度

const init = () => {
  let myChart = echarts.init(document.getElementById(props.id));
  let optionData = JSON.parse(JSON.stringify(sourceData.value));
  option.value = getPie3D(optionData, hollow.value);
  myChart.setOption(option.value);
  bindListen(myChart, optionData);
};
let seriesLabelLine = ref([]);
const getPie3D = (pieData, internalDiameterRatio) => {
  let series = [];
  let sumValue = 0;
  let k = 1 - internalDiameterRatio;
  for (let i = 0; i < pieData.length; i++) {
    sumValue += pieData[i].value;
    let seriesItem = {
      //系统名称
      name: pieData[i].name,
      type: "surface",
      //是否为参数曲面（是）
      parametric: true,
      //曲面图网格线（否）上面一根一根的
      wireframe: {
        show: false,
      },
      itemStyle: {
        color: pieData[i].itemStyle.color,
      },
      pieData: pieData[i],
      pieStatus: {
        selected: false,
        hovered: false,
        k: k,
      },
      center: ["50%", "100%"],
      
    };
    series.push(seriesItem);
  }
  let startValue = 0;
  let endValue = 0;
  let legendData = [];
  let legendBfb = [];
  legendData = [];
  legendBfb = [];
  console.log("ser", series);
  for (let i = 0; i < series.length; i++) {
    const source = sourceData.value;
    endValue = startValue + source[i].value;
    series[i].pieData.startRatio = startValue / sumValue;
    series[i].pieData.endRatio = endValue / sumValue;
    series[i].pieData.value =
      pieHeight.value > 0 ? pieHeight.value : series[i].pieData.value;

    series[i].parametricEquation = getParametricEquation(
      series[i].pieData.startRatio,
      series[i].pieData.endRatio,
      false,
      false,
      k,
      series[i].pieData.value
    );
    startValue = endValue;
    let bfb = fomatFloat(series[i].pieData.value / sumValue, 4);
    legendData.push({
      name: series[i].name,
      value: bfb,
    });
    legendBfb.push({
      name: series[i].name,
      value: bfb,
    });

    // let {
    //   //   itemStyle: { color },
    //   pieData: { startRatio, endRatio, value },
    // } = series[i];
    // addLabelLine(series, startRatio, endRatio, value, k, i);
  }
  //   series = series;
  //(第二个参数可以设置你这个环形的高低程度)
  let boxHeight = getHeight3D(series, 16); //通过传参设定3d饼/环的高度
  // 准备待返回的配置项，把准备好的 legendData、series 传入。
  let option = {
    //图例组件 //右侧展示的文案
    legend: {
      selectedMode: true,
      data: legendData,
      //图例列表的布局朝向。
      orient: "vertical",
      // right: 10,
      // top: 140,
      top: "middle",
      right: "5%",
      //图例文字每项之间的间隔
      itemGap: 15,
      textStyle: {
        color: "#333",
      },
      show: false,
      icon: "rect",
      // 这个可以显示百分比那种（可以根据你想要的来配置）
      formatter: function (param) {
        let item = pieData.filter((item) => item.name == param)[0];
        return `${item.name}`;
      },
    },

    //移动上去提示的文本内容(我没来得及改 你们可以根据需求改)
    tooltip: {
      formatter: (params) => {
        if (
          params.seriesName !== "mouseoutSeries" &&
          params.seriesName !== "pie2d"
        ) {
          return (
            `${params.seriesName}<br/>` +
            `${sourceData.value[params.seriesIndex].value}<br/>`
          );
        }
      },
    },
    itemStyle: {
      borderRadius: 5,
    },
    //这个可以变形
    xAxis3D: {
      min: -1,
      max: 1,
    },
    yAxis3D: {
      min: -1,
      max: 1,
    },
    zAxis3D: {
      min: -1,
      max: 1,
    },
    //此处是修改样式的重点
    grid3D: {
      show: false,
      boxHeight: boxHeight, //圆环的高度
      //这是饼图的位置
      top: "-30%",
      left: "-30%",
      width: "160%",
      height: "160%",

      viewControl: {
        //3d效果可以放大、旋转等，请自己去查看官方配置
        alpha: 35, //角度(这个很重要 调节角度的)
        distance: 300, //调整视角到主体的距离，类似调整zoom(这是整体大小)
        rotateSensitivity: true, //设置为0无法旋转
        zoomSensitivity: 0, //设置为0无法缩放
        panSensitivity: 0, //设置为0无法平移
        autoRotate: false, //自动旋转
      },
    },
    series: [...series, ...seriesLabelLine.value],
  };
  return option;
};
//获取3d丙图的最高扇区的高度
const getHeight3D = (series, height) => {
  if (pieHeight.value > 0) return pieHeight.value;
  series.sort((a, b) => {
    return b.pieData.value - a.pieData.value;
  });
  return (height * 35) / series[0].pieData.value;
};
// 生成扇形的曲面参数方程，用于 series-surface.parametricEquation
const getParametricEquation = (
  startRatio,
  endRatio,
  isSelected,
  isHovered,
  k,
  h
) => {
  // 计算
  let midRatio = (startRatio + endRatio) / 2;
  let startRadian = startRatio * Math.PI * 2;
  let endRadian = endRatio * Math.PI * 2;
  let midRadian = midRatio * Math.PI * 2;
  // 如果只有一个扇形，则不实现选中效果。
  if (startRatio === 0 && endRatio === 1) {
    isSelected = false;
  }
  // 通过扇形内径/外径的值，换算出辅助参数 k（默认值 1/3）
  k = typeof k !== "undefined" ? k : 1 / 3;

  // 计算选中效果分别在 x 轴、y 轴方向上的位移（未选中，则位移均为 0）
  let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
  let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
  // 计算高亮效果的放大比例（未高亮，则比例为 1）
  let hoverRate = isHovered ? 1.05 : 1;
  // 返回曲面参数方程
  return {
    u: {
      min: -Math.PI,
      max: Math.PI * 3,
      step: Math.PI / 32,
    },
    v: {
      min: 0,
      max: Math.PI * 2,
      step: Math.PI / 20,
    },
    x: function (u, v) {
      if (u < startRadian) {
        return (
          offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
        );
      }
      if (u > endRadian) {
        return (
          offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
        );
      }
      return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
    },
    y: function (u, v) {
      if (u < startRadian) {
        return (
          offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
        );
      }
      if (u > endRadian) {
        return (
          offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
        );
      }
      return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
    },
    z: function (u, v) {
      if (u < -Math.PI * 0.5) {
        return Math.sin(u);
      }
      if (u > Math.PI * 2.5) {
        return Math.sin(u) * h * 0.1;
      }
      return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
    },
  };
};
//这是一个自定义计算的方法
const fomatFloat = (num, n) => {
  var f = parseFloat(num);
  if (isNaN(f)) {
    return false;
  }
  f = Math.round(num * Math.pow(10, n)) / Math.pow(10, n); // n 幂
  var s = f.toString();
  var rs = s.indexOf(".");
  //判定如果是整数，增加小数点再补0
  if (rs < 0) {
    rs = s.length;
    s += ".";
  }
  while (s.length <= rs + n) {
    s += "0";
  }
  return s;
};
const bindListen = (myChart, optionData) => {
  let selectedIndex = "";
  let hoveredIndex = "";
  // 监听点击事件，实现选中效果（单选）
  myChart.on("click", function (params) {
    // 从 option.series 中读取重新渲染扇形所需的参数，将是否选中取反。
    let isSelected =
      !option.value.series[params.seriesIndex].pieStatus.selected;
    let isHovered = option.value.series[params.seriesIndex].pieStatus.hovered;
    let k = option.value.series[params.seriesIndex].pieStatus.k;
    let startRatio = option.value.series[params.seriesIndex].pieData.startRatio;
    let endRatio = option.value.series[params.seriesIndex].pieData.endRatio;
    // 如果之前选中过其他扇形，将其取消选中（对 option 更新）
    if (selectedIndex !== "" && selectedIndex !== params.seriesIndex) {
      option.value.series[selectedIndex].parametricEquation =
        getParametricEquation(
          option.value.series[selectedIndex].pieData.startRatio,
          option.value.series[selectedIndex].pieData.endRatio,
          false,
          false,
          k,
          option.value.series[selectedIndex].pieData.value
        );
      option.value.series[selectedIndex].pieStatus.selected = false;
    }
    // 对当前点击的扇形，执行选中/取消选中操作（对 option 更新）
    option.value.series[params.seriesIndex].parametricEquation =
      getParametricEquation(
        startRatio,
        endRatio,
        isSelected,
        isHovered,
        k,
        option.value.series[params.seriesIndex].pieData.value
      );
    option.value.series[params.seriesIndex].pieStatus.selected = isSelected;
    // 如果本次是选中操作，记录上次选中的扇形对应的系列号 seriesIndex
    isSelected ? (selectedIndex = params.seriesIndex) : null;
    // 使用更新后的 option，渲染图表
    myChart.setOption(option.value);
  });
  // 监听 mouseover，近似实现高亮（放大）效果
  myChart.on("mouseover", function (params) {
    // 准备重新渲染扇形所需的参数
    let isSelected;
    let isHovered;
    let startRatio;
    let endRatio;
    let k;
    // 如果触发 mouseover 的扇形当前已高亮，则不做操作
    if (hoveredIndex === params.seriesIndex) {
      return;
      // 否则进行高亮及必要的取消高亮操作
    } else {
      // 如果当前有高亮的扇形，取消其高亮状态（对 option 更新）
      if (hoveredIndex !== "") {
        // 从 option.series 中读取重新渲染扇形所需的参数，将是否高亮设置为 false。
        isSelected = option.value.series[hoveredIndex].pieStatus.selected;
        isHovered = false;
        startRatio = option.value.series[hoveredIndex].pieData.startRatio;
        endRatio = option.value.series[hoveredIndex].pieData.endRatio;
        k = option.value.series[hoveredIndex].pieStatus.k;
        // 对当前点击的扇形，执行取消高亮操作（对 option 更新）
        option.value.series[hoveredIndex].parametricEquation =
          getParametricEquation(
            startRatio,
            endRatio,
            isSelected,
            isHovered,
            k,
            option.value.series[hoveredIndex].pieData.value
          );
        option.value.series[hoveredIndex].pieStatus.hovered = isHovered;
        // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
        hoveredIndex = "";
      }
      // 如果触发 mouseover 的扇形不是透明圆环，将其高亮（对 option 更新）
      if (
        params.seriesName !== "mouseoutSeries" &&
        params.seriesName !== "pie2d"
      ) {
        // 从 option.series 中读取重新渲染扇形所需的参数，将是否高亮设置为 true。
        isSelected = option.value.series[params.seriesIndex].pieStatus.selected;
        isHovered = true;
        startRatio = option.value.series[params.seriesIndex].pieData.startRatio;
        endRatio = option.value.series[params.seriesIndex].pieData.endRatio;
        k = option.value.series[params.seriesIndex].pieStatus.k;
        // 对当前点击的扇形，执行高亮操作（对 option 更新）
        option.value.series[params.seriesIndex].parametricEquation =
          getParametricEquation(
            startRatio,
            endRatio,
            isSelected,
            isHovered,
            k,
            option.value.series[params.seriesIndex].pieData.value + 5
          );
        option.value.series[params.seriesIndex].pieStatus.hovered = isHovered;
        // 记录上次高亮的扇形对应的系列号 seriesIndex
        hoveredIndex = params.seriesIndex;
      }
      // 使用更新后的 option，渲染图表
      myChart.setOption(option.value);
    }
  });
  // 修正取消高亮失败的 bug
  myChart.on("globalout", function () {
    // 准备重新渲染扇形所需的参数
    let isSelected;
    let isHovered;
    let startRatio;
    let endRatio;
    let k;
    if (hoveredIndex !== "") {
      // 从 option.series 中读取重新渲染扇形所需的参数，将是否高亮设置为 true。
      isSelected = option.value.series[hoveredIndex].pieStatus.selected;
      isHovered = false;
      k = option.value.series[hoveredIndex].pieStatus.k;
      startRatio = option.value.series[hoveredIndex].pieData.startRatio;
      endRatio = option.value.series[hoveredIndex].pieData.endRatio;
      // 对当前点击的扇形，执行取消高亮操作（对 option 更新）
      option.value.series[hoveredIndex].parametricEquation =
        getParametricEquation(
          startRatio,
          endRatio,
          isSelected,
          isHovered,
          k,
          option.value.series[hoveredIndex].pieData.value
        );
      option.value.series[hoveredIndex].pieStatus.hovered = isHovered;
      // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
      hoveredIndex = "";
    }
    // 使用更新后的 option，渲染图表
    myChart.setOption(option.value);
  });

  myChart.on("legendselectchanged", function (event) {
    const { selected } = event; // 获取当前所有图例的选中状态（key:图例name，value:是否选中）
    const k = 1 - hollow.value; // 复用空心比例配置
    optionData.forEach((item) => (item.value = item.num));
    // 1. 筛选选中的原始数据（未选中则排除，选中为空时默认显示全部）
    const selectedData = Object.keys(selected).length
      ? optionData.filter((item) => selected[item.name])
      : [...optionData]; // 无选中时显示全部
    // 2. 重新计算筛选后数据的总数值（用于计算扇形比例）
    const newSumValue = selectedData.reduce((sum, item) => sum + item.value, 0);
    if (newSumValue === 0) return; // 避免无数据时出错

    // 3. 重置起始比例，遍历选中数据计算新的startRatio/endRatio
    let startValue = 0;
    const dataRatioMap = new Map(); // 存储{图例name: {startRatio, endRatio}}
    selectedData.forEach((item) => {
      const startRatio = startValue / newSumValue;
      const endRatio = (startValue + item.value) / newSumValue;
      dataRatioMap.set(item.name, { startRatio, endRatio });
      startValue += item.value;
    });

    // 4. 遍历所有3D扇形系列，更新配置（显示/隐藏、比例、3D形态）
    option.value.series.forEach((seriesItem) => {
      const seriesName = seriesItem.name;
      const isShow = selected[seriesName] ?? true; // 未选中项默认隐藏
      const pieData = optionData.find((item) => item.name === seriesName); // 关联原始数据
      const ratioInfo = dataRatioMap.get(seriesName) || {
        startRatio: 0,
        endRatio: 0,
      }; // 未选中项比例置0

      // 4.1 更新扇形基础信息（比例、高度）
      seriesItem.pieData = {
        ...pieData,
        startRatio: ratioInfo.startRatio,
        endRatio: ratioInfo.endRatio,
        value: pieHeight.value > 0 ? pieHeight.value : pieData.value, // 复用高度配置
      };

      // 4.2 更新3D参数方程（核心：控制扇形的3D形态和位置）
      seriesItem.parametricEquation = getParametricEquation(
        ratioInfo.startRatio, // 新起始比例
        ratioInfo.endRatio, // 新结束比例
        seriesItem.pieStatus.selected, // 保留原点击选中状态
        seriesItem.pieStatus.hovered, // 保留原hover状态
        k, // 空心比例
        seriesItem.pieData.value // 扇形高度
      );

      // 4.3 隐藏未选中的扇形（通过设置z轴范围使其不可见）
      if (!isShow) {
        seriesItem.parametricEquation.z = () => -2; // 低于可视范围（原配置z轴max=1）
      }
    });
    option.value.series[1].value = 100;
    // 5. 重渲染图表，显示完整3D饼图

    myChart.setOption(option.value);
  });
};

//数据中最大值
const maxValue = computed(() => {
  return Math.max(...sourceData.value.map((item) => item.value));
});
//高度动态缩放系数
const scaleZ = computed(() => {
  return (30 * 0.1) / maxValue.value;
});

//添加label指示线
/**
 * @param {*} series 配置
 * @param {*} startRatio 扇形起始位置比例
 * @param {*} endRatio 扇形结束位置比例
 * @param {*} value 数值
 * @param {*} k 辅助参数，饼图半径相关
 * @param {*} i  在series中索引
 * @param {*} color 指示线颜色
 */
const addLabelLine = (
  series,
  startRatio,
  endRatio,
  value,
  k,
  i,
  color = "#fff"
) => {
  //计算扇形中心弧度
  const midRadian = (startRatio + endRatio) * Math.PI;
  console.log(
    "midRadian",
    startRatio,
    endRatio,
    midRadian,
    scaleZ.value,
    maxValue.value
  );
  // 计算起点位置坐标（扇形边缘中心）
  let radius = 1 + k; // 外径
  const averageStartRatio = (startRatio + endRatio) / 2;
  console.log("averageStartRatio", averageStartRatio);
  //   if(averageStartRatio>0.2 && averageStartRatio<0.9){
  //     radius = 1.4
  //   }
  const posX = Math.cos(midRadian) * radius; //x坐标
  let posY = Math.sin(midRadian) * radius; //y坐标
  const posZ = value * scaleZ.value; //z坐标

  let flag =
    (midRadian >= 0 && midRadian <= Math.PI / 2) ||
    (midRadian >= (3 * Math.PI) / 2 && midRadian <= Math.PI * 2)
      ? 1
      : -1;

  //计算拐点坐标
  let turningPosArr = [posX * 1.0, posY * 1.4, posZ];
  //计算结束位置坐标
  let endPosArr = [posX * 1.9, posY * 1.5, posZ * 1];
  console.log("线", i, flag, posX, posY, posZ, turningPosArr, endPosArr);

  //添加label+指示线
  seriesLabelLine.value.push(
    //  指示线
    {
      type: "line3D",
      lineStyle: {
        color: "#D3D3D3", //线颜色
        width: 1, //线宽
      },
      data: [[posX, posY, posZ], turningPosArr, endPosArr],
    },
    //label
    {
      type: "scatter3D",
      label: {
        show: true,
        distance: 0,
        position: "right",
        textStyle: {
          color: "#777", //文字颜色
          //   backgroundColor: "rgba(0,0,0,0)", //透明背景
          fontSize: 13, //文字尺寸
          //   fontWeight: "bold", //文字加粗
          lineHeight: 16,
          padding: 5,
        },
        formatter: ({ name, value }) => {
          const dataItem = sourceData.value.find((item) => item.name === name);
          return `${name}\n${dataItem.value}`;
        },
      },
      symbolSize: 0,
      data: [
        {
          name: series[i].name,
          value: endPosArr,
        },
      ],
    }
  );
};

onMounted(() => {
  init();
});
</script>

<style scoped lang="scss">
.chartPie3D-box {
  width: 100%;
  height: 100%;
}
</style>
